﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using SpiceNet.Circuits;
using SpiceNet.Diagnostics;
using SpiceNet.Integration;

namespace SpiceNet.Simulations
{
    public class DcTran : Analysis
    {
        /// <summary>
        /// Analysis
        /// </summary>
        /// <param name="name"></param>
        public DcTran()
            : base(typeof(TranJob))
        {
            this.Name = "TRAN";
            this.Description = "Transient analysis";
            this.DoIc = true;
            this.Domain = DomainTypes.TimeDomain;
        }

        /// <summary>
        /// Initialize
        /// </summary>
        /// <param name="ckt"></param>
        public override void Init(Circuit ckt)
        {
            TranJob job = ckt.CurrentJob as TranJob;
            IntegrationMethod integ = ckt.Integration;
            integ.FinalTime = job.TRANfinalTime;
            integ.Step = job.TRANstep;
            integ.InitTime = job.TRANinitTime;
            integ.MaxStep = job.TRANmaxStep;
            if (integ.MaxStep == 0.0)
                integ.MaxStep = (ckt.Integration.FinalTime - ckt.Integration.InitTime) / 50.0;
            integ.Config.DelMin = 1e-9 * ckt.Integration.MaxStep;
            ckt.Mode = job.TRANmode;
        }

        /// <summary>
        /// Wraper for DCtran in dctran.c
        /// Execute the DC transient analysis
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="reset"></param>
        public override void Func(Circuit ckt, bool restart)
        {
            int i;
            double olddelta;
            double delta;
            double ndelta;
            int startIters;
            TimeSpan startselapsed;
            bool converged;
            bool firsttime;
            int save_order;
            Circuit.Modes save_mode;
            double mt;  /* temporary so macro call won't cross line boundry */

            CircuitState state = ckt.State;
            IntegrationMethod integ = ckt.Integration;
            TranJob curjob = (TranJob)ckt.CurrentJob;

            if (restart || state.Time == 0)
            {
                delta = Math.Min(integ.FinalTime / 50, integ.Step) / 10;

                integ.Breaks.Clear();
                integ.Breaks.SetBreakpoint(0.0);
                integ.Breaks.SetBreakpoint(integ.FinalTime);
                if (integ.Breaks.MinBreak == 0)
                    integ.Breaks.MinBreak = integ.MaxStep * 5e-5;

                // Initialize plots - Not implemented

                state.Time = 0;
                integ.Delta = 0;
                integ.Break = true;
                firsttime = true;
                save_mode = (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.TranOp | Circuit.Modes.InitJct;
                save_order = integ.Order;
                Op(ckt,
                        (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.TranOp | Circuit.Modes.InitJct,
                        (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.TranOp | Circuit.Modes.InitFloat,
                        ckt.Config.DcMaxIterations);
                ckt.Stat.TimePts++;
                integ.Order = 1;
                for (i = 0; i < integ.DeltaOld.Length; i++)
                    integ.DeltaOld[i] = integ.MaxStep;
                integ.Delta = delta;
                integ.SaveDelta = integ.FinalTime / 50.0;

                ckt.Mode = (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.Tran | Circuit.Modes.InitTran;
                /* modeinittran set here */
                state.States[0].CopyTo(state.States[1]);

                ckt.Stat.TranTime.Start();
                startIters = ckt.Stat.NumIter;
                startselapsed = ckt.Stat.SolveTime.Elapsed;
            }
            else
            {
                ckt.Stat.TranTime.Start();
                startIters = ckt.Stat.NumIter;
                startselapsed = ckt.Stat.SolveTime.Elapsed;
                if (integ.Breaks.MinBreak == 0)
                    integ.Breaks.MinBreak = integ.MaxStep * 5e-5;
                firsttime = false;
                goto resume;
            }

        /* 650 */
        nextTime:
            try
            {
                ckt.Accept();

                /* check if current breakpoint is outdated; if so, clear */
                while (state.Time > integ.Breaks.First)
                    integ.Breaks.ClearBreakpoint(ckt);

                /*
                 * Breakpoint handling scheme:
                 * When a timepoint t is accepted (by CKTaccept), clear all previous
                 * breakpoints, because they will never be needed again.
                 *
                 * t may itself be a breakpoint, or indistinguishably close. DON'T
                 * clear t itself; recognise it as a breakpoint and act accordingly
                 *
                 * if t is not a breakpoint, limit the timestep so that the next
                 * breakpoint is not crossed */
                ckt.Stat.Accepted++;
                integ.Break = false;
            }
            catch (CircuitException)
            {
                ckt.CurrentAnalysis = AnalysisType.DoingTran;
                ckt.Stat.TranTime.Stop();
                ckt.Stat.TranIter += ckt.Stat.NumIter - startIters;
                ckt.Stat.TranSolveTime += ckt.Stat.SolveTime.Elapsed.Subtract(startselapsed);
                throw;
            }
            if (state.Time >= integ.InitTime)
                Dump(ckt);
            ckt.Stat.OldIter = ckt.Stat.NumIter;
            if (Math.Abs(state.Time - integ.FinalTime) < integ.Breaks.MinBreak)
            {
                ckt.CurrentAnalysis = AnalysisType.DoingNone;
                ckt.Stat.TranTime.Stop();
                ckt.Stat.TranIter += ckt.Stat.NumIter - startIters;
                ckt.Stat.TranSolveTime += ckt.Stat.SolveTime.Elapsed.Subtract(startselapsed);
                return;
            }
        resume:
            integ.Delta = Math.Min(integ.Delta, integ.MaxStep);

            /* are we at a breakpoint, or indistinguishably close? */
            if ((state.Time == integ.Breaks.First) || ((integ.Breaks.First - state.Time) <= integ.Config.DelMin))
            {
                /* first timepoint after a breakpoint - cut integration order */
                /* and limit timestep to .1 times minimum of time to next breakpoint,
                 * and previous timestep */
                integ.Order = 1;

                mt = Math.Min(integ.SaveDelta, integ.Breaks.Delta); // *(integ.Breaks+1)-integ.Breaks.First);
                integ.Delta = Math.Min(integ.Delta, 0.1 * mt);
                if (firsttime)
                {
                    integ.Delta /= 10;
                }

                /* don't want to get below delmin for no reason */
                integ.Delta = Math.Max(integ.Delta, integ.Config.DelMin * 2.0);
            }
            else if (state.Time + integ.Delta >= integ.Breaks.First)
            {
                integ.SaveDelta = integ.Delta;
                integ.Delta = integ.Breaks.First - state.Time;
                integ.Break = true; /* why? the current pt. is not a bkpt. */
            }
            for (i = integ.DeltaOld.Length - 2; i >= 0; i--)
                integ.DeltaOld[i + 1] = integ.DeltaOld[i];
            integ.DeltaOld[0] = integ.Delta;

            // Cycle states
            state.CycleStates();

            /* 600 */
            while (true)
            {
                olddelta = integ.Delta;
                /* time abort? */
                state.Time += integ.Delta;
                integ.DeltaOld[0] = integ.Delta;

                integ.ComputeCoefficients(ckt);

                integ.Predict(ckt);

                save_mode = ckt.Mode;
                save_order = integ.Order;
                converged = !Iterate(ckt, ckt.Config.TranMaxIter); // Inverted!!! - Spice is not very logical sometimes...
                ckt.Stat.TimePts++;
                ckt.Mode = (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.Tran | Circuit.Modes.InitPred;
                if (firsttime)
                {
                    for (i = 0; i < state.States[0].Count; i++)
                    {
                        state.States[2][i] = state.States[1][i];
                        state.States[3][i] = state.States[1][i];
                    }
                }
                if (converged)
                {
                    state.Time = state.Time - integ.Delta;
                    ckt.Stat.Rejected++;
                    integ.Delta = integ.Delta / 8;
                    if (firsttime)
                    {
                        ckt.Mode = (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.Tran | Circuit.Modes.InitTran;
                    }
                    integ.Order = 1;
                }
                else
                {
                    if (firsttime)
                    {
                        firsttime = false;
                        goto nextTime; // no check on first time point
                    }
                    ndelta = integ.Delta;

                    try
                    {
                        ndelta = integ.Truncate(ckt);
                    }
                    catch (CircuitException)
                    {
                        ckt.CurrentAnalysis = AnalysisType.DoingTran;
                        ckt.Stat.TranTime.Stop();
                        ckt.Stat.TranIter += ckt.Stat.NumIter - startIters;
                        ckt.Stat.TranSolveTime += ckt.Stat.SolveTime.Elapsed.Subtract(startselapsed);
                        return;
                    }
                    if (ndelta > .9 * integ.Delta)
                    {
                        if (integ.Order == 1)
                        {
                            ndelta = integ.Delta;
                            integ.Order = 2;
                            try
                            {
                                ndelta = integ.Truncate(ckt);
                            }
                            catch (CircuitException)
                            {
                                ckt.CurrentAnalysis = AnalysisType.DoingTran;
                                ckt.Stat.TranTime.Stop();
                                ckt.Stat.TranIter += ckt.Stat.NumIter - startIters;
                                ckt.Stat.TranSolveTime += ckt.Stat.SolveTime.Elapsed.Subtract(startselapsed);
                                return;
                            }
                            if (ndelta <= 1.05 * integ.Delta)
                            {
                                integ.Order = 1;
                            }
                        }
                        /* time point OK  - 630*/
                        integ.Delta = ndelta;
                        /* go to 650 - trapezoidal */
                        goto nextTime;
                    }
                    else
                    {
                        state.Time = state.Time - integ.Delta;
                        ckt.Stat.Rejected++;
                        integ.Delta = ndelta;
                    }
                }
                if (integ.Delta <= integ.Config.DelMin)
                {
                    if (olddelta > integ.Config.DelMin)
                    {
                        integ.Delta = integ.Config.DelMin;
                    }
                    else
                    {
                        ckt.CurrentAnalysis = AnalysisType.DoingTran;
                        ckt.Stat.TranTime.Stop();
                        ckt.Stat.TranIter += ckt.Stat.NumIter - startIters;
                        ckt.Stat.TranSolveTime += ckt.Stat.SolveTime.Elapsed.Subtract(startselapsed);
                        throw new CircuitException(string.Format("Timestep too small at time {0}", state.Time));
                    }
                }
            }
        }
    }
}
